#include <sys/select.h>

#include <sstream>

#include "routing/routing_io.h"
#include "logger.h"
#include "routing/ip_packet.h"

#undef VERBOSE_DEBUG

const std::string CRoutingIO::sIniSectionName = "RTP";
const std::string CRoutingIO::sConfAddressEmbedee = "ADDRESS_EMBEDEE";
const std::string CRoutingIO::sConfAddressExtractee = "ADDRESS_EXTRACTEE";
const std::string CRoutingIO::sConfNfqueueNum = "NFQUEUE_NUM";
const int CRoutingIO::sBytesPerLine = 36;

int CRoutingIO::init(CPropertiesReader *pConfig)
{
	printl(LT_STDERR, __FILE__, "Initializing the Routing-IO. \n");

	/* save the properties reader */
	mConfig = pConfig;

	/* read out configured addresses for the routing instance */

	/* NFQUEUE setup */
	mNfq_handle = nfq_open();
	if(mNfq_handle == NULL)
	{
		printl(LT_STDERR, __FILE__, "Failed to create the nfqueue handle. \n");
		return -1;
	}
	nfq_unbind_pf(mNfq_handle, AF_INET);
	if(nfq_bind_pf(mNfq_handle, AF_INET) < 0)
	{
		printl(LT_STDERR, __FILE__, "Failed to bind nfqueue on AF_INET. \n");
		return -1;
	}

	int lNfqueueNum = 0;
	std::string lString;

	if (mConfig->containsKey(sIniSectionName, sConfNfqueueNum)) 
	{
		if (mConfig->getValue(sIniSectionName, sConfNfqueueNum, lString)) 
		{
			std::stringstream lStringStream(lString);
			lStringStream >> lNfqueueNum;
			if (lStringStream.fail())
			{
				return -1;
			}
			if(lNfqueueNum < 0) 
			{
				return -1;
			}
			mNfq_q_handle = nfq_create_queue(
					mNfq_handle, 
					lNfqueueNum,
					CRoutingIO::callback,
					static_cast<void*>(this)); 
			if(mNfq_q_handle == NULL)
			{
				printl(LT_STDERR, __FILE__, "Failed to set nfqueue callback with queue %d. \n", lNfqueueNum);
				return -1;
			}
		}
	}
	else
	{
		printl(LT_STDERR, __FILE__, "No config-directive. There using %s = %d. \n", sConfNfqueueNum.c_str(), lNfqueueNum);
		mNfq_q_handle = nfq_create_queue(
				mNfq_handle, 
				lNfqueueNum,
				CRoutingIO::callback,
				static_cast<void*>(this)); 
		if(mNfq_q_handle == NULL)
		{
			printl(LT_STDERR, __FILE__, "Failed to set nfqueue callback with queue %d. \n", lNfqueueNum);
			return -1;
		}
	}
	if(nfq_set_mode(mNfq_q_handle, NFQNL_COPY_PACKET, CIPPacket::sMaxPacketLength) < 0)
	{
		printl(LT_STDERR, __FILE__, "Failed to set nfqueue mode to \"copy packet\". \n");
		return -1;
	}

	mNfq_fd = nfq_fd(mNfq_handle);

	if (get_address(sIniSectionName.c_str(), sConfAddressEmbedee.c_str(), &mAddressEmbeddee) < 0 ||
			get_address(sIniSectionName.c_str(), sConfAddressExtractee.c_str(), &mAddressExtractee) < 0)
	{
		printl(LL_ERR, __FILE__, "Address of embeddee misconfigured (not found?) in "
				"the configuration file.\n");
		return -1;
	}
	else
	{
		printl(LL_DEBUG, __FILE__, "Configured addresses of embedee (0x%x) and "
				"extractee (0x%x).\n", htonl(mAddressEmbeddee), htonl(mAddressExtractee));
	}

	if (pConfig->containsKey(sIniSectionName, "EXPECTED_CODEC"))
	{
		if (pConfig->getValue(sIniSectionName, "EXPECTED_CODEC", lString))
		{
			if (lString == "PCM")
			{
				mStreamInfo.mCodec = NAudioFormats::PCM;
			}
			else if (lString == "ALAW")
			{
				mStreamInfo.mCodec = NAudioFormats::ALAW;
			}
			else if (lString == "ULAW")
			{
				mStreamInfo.mCodec = NAudioFormats::ULAW;
			}
		}
	}

	return 0;
}

int CRoutingIO::destroy()
{
	printl(LT_STDERR, __FILE__, "cleaning up nfqueue-part. \n");
	nfq_destroy_queue(mNfq_q_handle);
	nfq_close(mNfq_handle);
 
	return 0; /* success */
}
int CRoutingIO::read(void** pData, EAction& pAction, int pTimeoutIO)
{
#if 0
	printl(LT_STDERR, __FILE__, "CRoutingIO::read(%p, %d, %d) invoked. \n",
			pData,
			pAction,
			pTimeoutIO);
#endif
	/* TODO correct length problematics */
	int lTimeoutCtr = 0;
	const int lTimeoutSelect = 200000; /* usec */
	struct timeval lTimeoutVal = { 0, lTimeoutSelect };
	fd_set lFDSet;
	int lReturn = 0;
	int lBytes = 0;
	char lBuffer[CIPPacket::sMaxPacketLength];
	int lLength = 0;

	/* default action here is none */
	pAction = NO_ACTION;

	/* reset marker for available packets */
	mPacketReady = false;

	/* loop over processing networking data until the callback has been invoked */
	while(lTimeoutCtr < pTimeoutIO && mPacketReady == false)
	{
		FD_ZERO(&lFDSet);
		FD_SET(mNfq_fd, &lFDSet);
		lTimeoutVal.tv_sec = 0;
		lTimeoutVal.tv_usec = lTimeoutSelect;
		lReturn = select(mNfq_fd + 1,
				&lFDSet,
				NULL,
				NULL,
				&lTimeoutVal);
		if(lReturn == -1)
		{
			/* error */
		}
		else if(lReturn && FD_ISSET(mNfq_fd, &lFDSet))
		{
			/* something has arrived to be read */
#if 0
			printl(LL_DEBUG, __FILE__, "recv(%d, %p, %d, %d) \n",
					mNfq_fd, lBuffer, CIPPacket::sMaxPacketLength, 0);
#endif
			lBytes = recv(mNfq_fd, lBuffer, CIPPacket::sMaxPacketLength, 0);
			if(lBytes >= 0)
			{
				nfq_handle_packet(mNfq_handle, lBuffer, lBytes);
			}
			else
			{
				printl(LT_STDERR, __FILE__, "Failed to read from nfqueue. Continuing! \n");
				lTimeoutCtr += lTimeoutSelect;
				continue;
			}
		}
		else
		{
			/* timeout reached */
			lTimeoutCtr += lTimeoutSelect;
		}
	}

	if(mPacketReady)
	{
		pAction = mAction;

		switch(mAction)
		{
			case EMBED:
			case EXTRACT:
				(*pData) = mRTPPacket->getPayload();
				lLength = mRTPPacket->getPayloadLength();
				break;
			case NO_ACTION:
					lLength = 0;
				break;
			case UNKNOWN_ACTION:
			default:
					lLength = -1; /* error case */
				break;
		}
#if VERBOSE_DEBUG
		printCurrentPacket(mIPPacket->getPacketBuffer(), mIPPacket->getPacketLength());
#endif
	}

	return lLength;
}

int CRoutingIO::write(void* pData, const EAction& pAction, int pLength)
{
	/* pLength denotes the length of samples data, not the real packet length */

	int lReturn = 0;

	mIPPacket->calcChecksum();

#ifdef VERBOSE_DEBUG
	printl(LL_DEBUG, __FILE__, "Writing RTP-Packet. IP-Src: %08x, IP-Dst: %08x, RTP-Sequence #: 0x%02x(%u) \n", 
			ntohl(mIPPacket->getSrcIP()),
			ntohl(mIPPacket->getDstIP()),
			mRTPPacket->getSequenceNumber(),
			mRTPPacket->getSequenceNumber());
#endif

#ifdef VERBOSE_DEBUG
	printCurrentPacket(mIPPacket->getPacketBuffer(), mIPPacket->getPacketLength());
#endif

	/* see sendmsg(2) for return value */
	lReturn = nfq_set_verdict(mNfq_q_handle,
			mPacketID,
			NF_ACCEPT,
			mIPPacket->getPacketLength(), 
			static_cast<unsigned char*>(mIPPacket->getPacketBuffer()));

	CRTPPacket::destroyRTPPacket(mRTPPacket);
	CIPPacket::destroyIPPacket(mIPPacket);

	if(lReturn > pLength)
	{
		lReturn = pLength;
	}

	return lReturn; 
}

int CRoutingIO::callback(struct nfq_q_handle *pNfq_q_handle, struct nfgenmsg* pNf_gen_msg, struct nfq_data* pNfq_data, void* pData)
{
#if 0
	printl(LT_STDERR, __FILE__, "CRoutingIO::callback(%p, %p, %p, %p) invoked. \n",
			pNfq_q_handle,
			pNf_gen_msg,
			pData);
#endif
	CRoutingIO* lThis = static_cast<CRoutingIO*>(pData);
	return lThis->callback(pNfq_q_handle, pNf_gen_msg, pNfq_data);
}

int CRoutingIO::callback(struct nfq_q_handle *pNfq_q_handle, struct nfgenmsg* pNf_gen_msg, struct nfq_data* pNfq_data)
{
	char* lPacket_bin_data;
	struct nfqnl_msg_packet_hdr* lPacket_header = nfq_get_msg_packet_hdr(pNfq_data);
	char* lPacketData;
	int lReturn = 0; /* success (?) */

	mPacketReady = false;

	if(lPacket_header != NULL)
	{
		mPacketID = ntohl(lPacket_header->packet_id);
		mPacketLength = nfq_get_payload(pNfq_data, &lPacketData);
		if(mPacketLength < 0)
		{
			return -1; /* error */
		}
		mIPPacket = CIPPacket::createIPPacket(reinterpret_cast<uint8_t*>(lPacketData), mPacketLength);
		if(mIPPacket != NULL)
		{
			mRTPPacket = CRTPPacket::createRTPPacket(mIPPacket);
			if(mRTPPacket != NULL)
			{
				mStreamInfo = NAudioFormats::SStreamInfo(
						NAudioFormats::U8, /* only codecs with a sample-width of 8 bits supported */
						8000,
						mRTPPacket->getPayloadType());

#ifdef VERBOSE_DEBUG
				printl(LL_DEBUG, __FILE__, "Processing RTP-Packet. IP-Src: %08x, IP-Dst: %08x, RTP-Sequence #: 0x%02x(%u) \n", 
						ntohl(mIPPacket->getSrcIP()),
						ntohl(mIPPacket->getDstIP()),
						mRTPPacket->getSequenceNumber(),
						mRTPPacket->getSequenceNumber());
#endif
				/* set action (embedding, extracting, none) */
				if(mIPPacket->getSrcIP() == mAddressEmbeddee &&
						mIPPacket->getDstIP() == mAddressExtractee)
				{
#ifdef VERBOSE_DEBUG
					printl(LL_DEBUG, __FILE__, "Embedding: 0x%08x(src) == 0x%08x(embed) && 0x%08x(dst) == 0x%08x(extract). \n",
							mIPPacket->getSrcIP(), mAddressEmbeddee, mIPPacket->getDstIP(), mAddressExtractee);
#endif
					mAction = EMBED;
				}
				else if(mIPPacket->getSrcIP() == mAddressExtractee &&
						mIPPacket->getDstIP() == mAddressEmbeddee)
				{
#ifdef VERBOSE_DEBUG
					printl(LL_DEBUG, __FILE__, "Extracting: 0x%08x(src) == 0x%08x(extract) && 0x%08x(dst) == 0x%08x(embed). \n",
							mIPPacket->getSrcIP(), mAddressExtractee, mIPPacket->getDstIP(), mAddressEmbeddee);
#endif
					mAction = EXTRACT;
				}
				else
				{
					mAction = NO_ACTION;
				}

				mPacketReady = true;
			}
			if(mRTPPacket == NULL || 
					(mPacketReady == true && mAction == NO_ACTION))
			{
				/* forward the packet, it is of no use to us */
				mPacketReady = false;
				lReturn = nfq_set_verdict(pNfq_q_handle,
						mPacketID,
						NF_ACCEPT,
						mPacketLength, 
#if 1
						reinterpret_cast<unsigned char*>(mIPPacket->getPacketBuffer()));
#else
						reinterpret_cast<unsigned char*>(lPacketData));
#endif
				if(mRTPPacket != NULL)
				{
					CRTPPacket::destroyRTPPacket(mRTPPacket);
				}
				CIPPacket::destroyIPPacket(mIPPacket);
			}
		}
		else
		{
			lReturn = -1; /* error, only IP packets can be processed */
		}
	}
	else
	{
		lReturn = -1; /* error */
	}

	return lReturn; 
}

int32_t CRoutingIO::get_address(const char* pSection, const char* pKey, uint32_t* pAddress)
{
  std::string lAddress;
  uint8_t lOctets[4] = { 0, 0, 0, 0 };
  int8_t lCnt = 0;

  if(mConfig->containsKey(pSection, pKey))
  {
    mConfig->getValue(pSection, pKey, lAddress);

    /* convert from string representation to binary presentation */
    if(sscanf(lAddress.c_str(), "%hhu.%hhu.%hhu.%hhu",
          lOctets,
          lOctets + 1,
          lOctets + 2,
          lOctets + 3) != 4)
    {
      return -1; /* error */
    }

    (*pAddress) = 0;
    for(lCnt = 3; lCnt >= 0; --lCnt)
    {
      (*pAddress) <<= 8;
      (*pAddress) |= *(lOctets + lCnt);
    }

    printl(LT_STDERR, __FILE__,
        "Converted address of %s: %08X (%s)\n",
        pKey,
        (*pAddress),
        lAddress.c_str());
  }
  else
  {
    return -1; /* error */
  }

  return 0; /* success */
}

void CRoutingIO::printCurrentPacket(uint8_t* pData, uint16_t pLength)
{
  uint8_t lLine_cnt = 0;
  uint16_t lCnt = 0;
  uint32_t lString_position_packet = 0;
  uint32_t lString_position_printable = 0;
  char lBuffer[10];

  for(; lCnt < pLength; ++lCnt)
  {
#if HEXDUMP
    fprintf(stderr, "%02X ", (uint8_t)pData[lCnt]);
#else
    if(isgraph(pData[lCnt]))
    {   
      fprintf(stderr, "%c", pData[lCnt]);
    }   
    else
    {   
      fprintf(stderr, ".");
    }   
#endif /* HEXDUMP */
    if((lCnt + 1) % sBytesPerLine == 0)
    {   
      fprintf(stderr, "\n");
    }   
  }

  fprintf(stderr, "\n");
}

